]> git.saurik.com Git - apple/security.git/blob - Security/Keychain Circle Notification/KNAppDelegate.m
Security-57031.40.6.tar.gz
[apple/security.git] / Security / Keychain Circle Notification / KNAppDelegate.m
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
32
33 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
34 #import <AOSAccounts/MobileMePrefsCore.h>
35
36 static char *kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
37 static const NSString *kKickedOutKey = @"KickedOut";
38 static const NSString *kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
39
40 @implementation KNAppDelegate
41
42 static NSUserNotificationCenter *appropriateNotificationCenter()
43 {
44 return [NSUserNotificationCenter _centerForIdentifier:@"com.apple.security.keychain-circle-notification" type:_NSUserNotificationCenterTypeSystem];
45 }
46
47 -(void)notifyiCloudPreferencesAbout:(NSString *)eventName;
48 {
49 if (nil == eventName) {
50 return;
51 }
52
53 NSString *account = (__bridge NSString *)(MMCopyLoggedInAccount());
54 NSLog(@"notifyiCloudPreferencesAbout %@", eventName);
55
56 AEDesc aeDesc;
57 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *)kMMServiceIDKeychainSync, eventName, account, &aeDesc);
58 if (createdAEDesc)
59 {
60 OSErr err;
61 LSLaunchURLSpec lsSpec;
62
63 lsSpec.appURL = NULL;
64 lsSpec.itemURLs = (__bridge CFArrayRef)([NSArray arrayWithObject:[NSURL fileURLWithPath:@"/System/Library/PreferencePanes/iCloudPref.prefPane"]]);
65 lsSpec.passThruParams = &aeDesc;
66 lsSpec.launchFlags = kLSLaunchDefaults | kLSLaunchAsync;
67 lsSpec.asyncRefCon = NULL;
68
69 err = LSOpenFromURLSpec(&lsSpec, NULL);
70
71 if (err) {
72 NSLog(@"Can't send event %@, err=%d", eventName, err);
73 }
74 AEDisposeDesc(&aeDesc);
75 }
76 else
77 {
78 NSLog(@"unable to create and send aedesc for account: '%@' and action: '%@'\n", account, eventName);
79 }
80 }
81
82 -(void)showiCloudPrefrences
83 {
84 static NSAppleScript *script = nil;
85 if (!script) {
86 script = [[NSAppleScript alloc] initWithSource:@"tell application \"System Preferences\"\n\
87 activate\n\
88 set the current pane to pane id \"com.apple.preferences.icloud\"\n\
89 end tell"];
90 }
91
92 NSDictionary *appleScriptError = nil;
93 [script executeAndReturnError:&appleScriptError];
94
95 if (appleScriptError) {
96 NSLog(@"appleScriptError: %@", appleScriptError);
97 } else {
98 NSLog(@"NO appleScript error");
99 }
100 }
101
102 -(void)timerCheck
103 {
104 NSDate *nowish = [NSDate new];
105 self.state = [KNPersistantState loadFromStorage];
106 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
107 NSLog(@"REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
108 // self.circle.rawStatus might not be valid yet
109 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
110 // Still have a request pending, send reminder, and also in addtion to the UI
111 // we need to send a notification for iCloud pref pane to pick up
112
113 CFNotificationCenterPostNotificationWithOptions(CFNotificationCenterGetDistributedCenter(), CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"), (__bridge const void *)([self.state.applcationDate description]), NULL, 0);
114
115 [self postApplicationReminder];
116 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
117 [self.state writeToStorage];
118 }
119 }
120 }
121
122 -(void)scheduleActivityAt:(NSDate*)time
123 {
124 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
125 NSTimeInterval howSoon = [time timeIntervalSinceNow];
126 if (howSoon > 0) {
127 [self scheduleActivityIn:howSoon];
128 } else {
129 [self timerCheck];
130 }
131 }
132 }
133
134 -(void)scheduleActivityIn:(int)alertInterval
135 {
136 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
137 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
138 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
139 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REPEATING, false);
140 xpc_dictionary_set_bool(options, XPC_ACTIVITY_ALLOW_BATTERY, true);
141 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
142
143 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
144 [self timerCheck];
145 });
146 }
147
148 -(NSTimeInterval)getPendingApplicationReminderInterval
149 {
150 if (self.state.pendingApplicationReminderInterval) {
151 return [self.state.pendingApplicationReminderInterval doubleValue];
152 } else {
153 return 48*24*60*60;
154 }
155 }
156
157 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
158 {
159 appropriateNotificationCenter().delegate = self;
160
161 NSLog(@"Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
162
163 self.viewedIds = [NSMutableSet new];
164 self.circle = [KDSecCircle new];
165 self.state = [KNPersistantState loadFromStorage];
166 KNAppDelegate *me = self;
167
168 [self.circle addChangeCallback:^{
169 me.state = [KNPersistantState loadFromStorage];
170 if ((me.state.lastCircleStatus == kSOSCCInCircle && !me.circle.isInCircle) || me.state.debugLeftReason) {
171 enum DepartureReason reason = kSOSNeverLeftCircle;
172 if (me.state.debugLeftReason) {
173 reason = [me.state.debugLeftReason intValue];
174 me.state.debugLeftReason = nil;
175 } else {
176 CFErrorRef err = NULL;
177 reason = SOSCCGetLastDepartureReason(&err);
178 if (reason == kSOSDepartureReasonError) {
179 NSLog(@"SOSCCGetLastDepartureReason err: %@", err);
180 }
181 }
182
183 //NSString *model = (__bridge NSString *)(ASI_CopyComputerModelName(FALSE));
184 NSString *body = nil;
185 switch (reason) {
186 case kSOSDepartureReasonError:
187 case kSOSNeverLeftCircle:
188 case kSOSWithdrewMembership:
189 break;
190
191 default:
192 NSLog(@"Unknown departure reason %d", reason);
193 // fallthrough on purpose
194
195 case kSOSMembershipRevoked:
196 case kSOSLeftUntrustedCircle:
197 body = NSLocalizedString(@"Approve this Mac from another device to use iCloud Keychain.", @"Body for iCloud Keychain Reset notification");
198 break;
199 }
200 [me.state writeToStorage];
201 NSLog(@"departure reason %d, body=%@", reason, body);
202 if (body) {
203 [me postKickedOutWithMessage: body];
204 }
205 } else if (me.circle.isInCircle) {
206 // We are in a circle, so we should get rid of any reset notifications that are hanging out
207 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
208 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
209 if (note.userInfo[kValidOnlyOutOfCircleKey]) {
210 NSLog(@"Removing existing notification (%@) now that we are in circle", note);
211 [appropriateNotificationCenter() removeDeliveredNotification: note];
212 }
213 }
214 }
215
216 [me timerCheck];
217
218 if (me.state.lastCircleStatus != kSOSCCRequestPending && me.circle.rawStatus == kSOSCCRequestPending) {
219 NSLog(@"Entered RequestPending");
220 NSDate *nowish = [NSDate new];
221 me.state.applcationDate = nowish;
222 me.state.pendingApplicationReminder = [me.state.applcationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
223 [me.state writeToStorage];
224 [me scheduleActivityAt:me.state.pendingApplicationReminder];
225 }
226
227 NSMutableSet *applicantIds = [NSMutableSet new];
228 for (KDCirclePeer *applicant in me.circle.applicants) {
229 if (!me.circle.isInCircle) {
230 // We don't want to yammer on about circles we aren't in,
231 // and we don't want to be extra confusing announcing our
232 // own join requests as if the user could approve them
233 // locally!
234 break;
235 }
236 [me postForApplicant:applicant];
237 [applicantIds addObject:applicant.idString];
238 }
239
240 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
241 NSLog(@"Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
242 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
243 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
244 NSLog(@"No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
245 [notificationCenter removeDeliveredNotification:note];
246 } else {
247 NSLog(@"Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
248 }
249 }
250
251 me.state.lastCircleStatus = me.circle.rawStatus;
252
253 [me.state writeToStorage];
254 }];
255
256 [me scheduleActivityAt:me.state.pendingApplicationReminder];
257 }
258
259 -(BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
260 {
261 return YES;
262 }
263
264 -(void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
265 {
266 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
267 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
268 }
269
270 // The "Later" seems handled Ok without doing anything here, but KickedOut & other special items need an action
271 if (notification.userInfo[@"SPECIAL"]) {
272 NSLog(@"ACTIVATED (remove): %@", notification);
273 [appropriateNotificationCenter() removeDeliveredNotification:notification];
274 } else {
275 NSLog(@"ACTIVATED (NOT removed): %@", notification);
276 }
277 }
278
279 -(void)userNotificationCenter:(NSUserNotificationCenter *)center didDismissAlert:(NSUserNotification *)notification
280 {
281 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Dismiss"]];
282
283 if (!notification.userInfo[@"SPECIAL"]) {
284 // If we don't do anything here & another notification comes in we
285 // will repost the alert, which will be dumb.
286 id applicantId = notification.userInfo[@"applicantId"];
287 if (applicantId != nil) {
288 [self.viewedIds addObject:applicantId];
289 }
290 NSLog(@"DISMISS (t) %@", notification);
291 } else {
292 NSLog(@"DISMISS (f) %@", notification);
293 [appropriateNotificationCenter() removeDeliveredNotification:notification];
294 }
295 }
296
297 -(void)postForApplicant:(KDCirclePeer*)applicant
298 {
299 static int postCount = 0;
300
301 if ([self.viewedIds containsObject:applicant.idString]) {
302 NSLog(@"Already viewed %@, skipping", applicant);
303 return;
304 }
305
306 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
307 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
308 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
309 if (note.isPresented) {
310 NSLog(@"Already posted&presented: %@ (I=%@)", note, note.userInfo);
311 return;
312 } else {
313 NSLog(@"Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
314 }
315 }
316 }
317
318 NSUserNotification *note = [NSUserNotification new];
319
320 // Genstrings command line is: genstrings -o en.lproj -u KNAppDelegate.m
321 note.title = [NSString stringWithFormat:NSLocalizedString(@"iCloud Keychain", @"Title for new keychain syncing device notification")];
322 note.informativeText = [NSString stringWithFormat:NSLocalizedString(@"\\U201C%1$@\\U201D wants to use your passwords.", @"Message text for new keychain syncing device notification"), applicant.name];
323
324 note.hasActionButton = YES;
325 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
326 note._identityImage = [NSImage bundleImage];
327 note._identityImageHasBorder = NO;
328 note._actionButtonIsSnooze = YES;
329 note.actionButtonTitle = NSLocalizedString(@"Later", @"Button label to dismiss device notification");
330 note.otherButtonTitle = NSLocalizedString(@"View", @"Button label to view device notification");
331
332 note.identifier = [[NSUUID new] UUIDString];
333
334 note.userInfo = @{@"applicantName": applicant.name,
335 @"applicantId": applicant.idString,
336 @"Dismiss": (__bridge NSString *)kMMPropertyKeychainAADetailsAEAction,
337 };
338
339 NSLog(@"About to post#%d/%lu (%@): %@", postCount, (unsigned long)noteCenter.deliveredNotifications.count, applicant.idString, note);
340 [appropriateNotificationCenter() deliverNotification:note];
341
342 postCount++;
343 }
344
345 -(void)postKickedOutWithMessage:(NSString*)body
346 {
347 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
348 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
349 if (note.userInfo[kKickedOutKey]) {
350 if (note.isPresented) {
351 NSLog(@"Already posted&presented (removing): %@", note);
352 [appropriateNotificationCenter() removeDeliveredNotification: note];
353 } else {
354 NSLog(@"Already posted, but not presented: %@", note);
355 }
356 }
357 }
358
359 NSUserNotification *note = [NSUserNotification new];
360
361 note.title = NSLocalizedString(@"iCloud Keychain Was Reset", @"Title for iCloud Keychain Reset notification");
362 note.informativeText = body; // Already LOCed
363
364 note._identityImage = [NSImage bundleImage];
365 note._identityImageHasBorder = NO;
366 note.otherButtonTitle = NSLocalizedString(@"Close", @"Close button");
367 note.actionButtonTitle = NSLocalizedString(@"Options", @"Options Button");
368
369 note.identifier = [[NSUUID new] UUIDString];
370
371 note.userInfo = @{kKickedOutKey: @1,
372 kValidOnlyOutOfCircleKey: @1,
373 @"SPECIAL": @1,
374 @"Activate": (__bridge NSString *)kMMPropertyKeychainMRDetailsAEAction,
375 };
376
377 NSLog(@"About to post#-/%lu (KICKOUT): %@", (unsigned long)noteCenter.deliveredNotifications.count, note);
378 [appropriateNotificationCenter() deliverNotification:note];
379 }
380
381 -(void)postApplicationReminder
382 {
383 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
384 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
385 if (note.userInfo[@"ApplicationReminder"]) {
386 if (note.isPresented) {
387 NSLog(@"Already posted&presented (removing): %@", note);
388 [appropriateNotificationCenter() removeDeliveredNotification: note];
389 } else {
390 NSLog(@"Already posted, but not presented: %@", note);
391 }
392 }
393 }
394
395 NSUserNotification *note = [NSUserNotification new];
396
397 note.title = NSLocalizedString(@"iCloud Keychain", @"Title for iCloud Keychain Application still pending (from this device) reminder");
398 note.informativeText = NSLocalizedString(@"Approve this Mac from another device to use iCloud Keychain.", @"Body text for iCloud Keychain Application still pending (from this device) reminder");
399
400 note._identityImage = [NSImage bundleImage];
401 note._identityImageHasBorder = NO;
402 note.otherButtonTitle = NSLocalizedString(@"Close", @"Close button");
403 note.actionButtonTitle = NSLocalizedString(@"Options", @"Options Button");
404
405 note.identifier = [[NSUUID new] UUIDString];
406
407 note.userInfo = @{@"ApplicationReminder": @1,
408 kValidOnlyOutOfCircleKey: @1,
409 @"SPECIAL": @1,
410 @"Activate": (__bridge NSString *)kMMPropertyKeychainWADetailsAEAction,
411 };
412
413 NSLog(@"About to post#-/%lu (REMINDER): %@ (I=%@)", (unsigned long)noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
414 [appropriateNotificationCenter() deliverNotification:note];
415 }
416
417 @end